package io.modifier.basic.compose.hooks

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import io.modifier.basic.compose.hooks.comm.OnEndCallback
import io.modifier.basic.compose.hooks.comm.Options
import io.modifier.basic.compose.hooks.comm.invoke
import io.modifier.basic.compose.hooks.data.useGetState
import io.modifier.basic.compose.hooks.data.useState
import io.modifier.basic.compose.hooks.time.useInterval
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlinx.datetime.Instant
import io.modifier.basic.compose.hooks.utils.asBoolean
import io.modifier.basic.compose.hooks.utils.currentTime

/*
  Description: A hook for manage countdown.
*/

/**
 * Countdown options
 *
 * @constructor Create empty Countdown options
 * @property leftTime directly set the remaining time of the countdown
 * @property targetDate Lower priority than [leftTime], set a target time
 *     for the end of the countdown
 * @property interval countdown interval
 * @property onEnd callback function for end of countdown
 */
@Stable
data class CountdownOptions internal constructor(
    var leftTime: Duration? = null,
    var targetDate: Instant? = null,
    var interval: Duration = 1.seconds,
    var onEnd: OnEndCallback? = null,
) {
    companion object : Options<CountdownOptions>(::CountdownOptions)
}

@Composable
private fun useCountdown(options: CountdownOptions): CountdownHolder {
    val (leftTime, targetDate, interval, onEnd) = options
    require(leftTime.asBoolean() || targetDate.asBoolean()) {
        "'leftTime' or 'targetDate' must be set"
    }
    val target = useCreation {
        if (leftTime.asBoolean()) {
            currentTime + leftTime
        } else {
            targetDate
        }
    }.current

    val (timeLeft, setTimeLeft) = useGetState(calcLeft(target))
    val onEndRef by useLatestRef(value = onEnd)
    var pauseRef by useRef(default = {})
    val (resume, pause) = useInterval(
        optionsOf = {
            period = interval
        }
    ) {
        val targetLeft = calcLeft(target)
        setTimeLeft.invoke(targetLeft)
        if (targetLeft == 0.seconds) {
            pauseRef()
            onEndRef?.invoke()
        }
    }
    pauseRef = pause
    useEffect(interval) {
        if (!target.asBoolean()) {
            setTimeLeft.invoke(0.seconds)
            return@useEffect
        }
        setTimeLeft.invoke(calcLeft(target))
        resume()
    }
    val formatRes = useState { parseDuration(timeLeft.value) }
    return remember { CountdownHolder(timeLeft, formatRes) }
}

@Composable
fun useCountdown(optionsOf: CountdownOptions.() -> Unit): CountdownHolder = useCountdown(remember { CountdownOptions.optionOf(optionsOf) })

@Stable
private fun calcLeft(target: Instant?): Duration {
    if (target == null) return 0.seconds
    val left = target - currentTime
    return if (left < 0.seconds) 0.seconds else left
}

@Stable
data class FormattedRes(
    val days: Int,
    val hours: Int,
    val minutes: Int,
    val seconds: Int,
    val milliseconds: Int,
)

@Stable
private fun parseDuration(leftTime: Duration): FormattedRes = FormattedRes(
    days = (leftTime.inWholeDays).toInt(),
    hours = ((leftTime.inWholeHours) % 24).toInt(),
    minutes = ((leftTime.inWholeMinutes) % 60).toInt(),
    seconds = ((leftTime.inWholeSeconds) % 60).toInt(),
    milliseconds = (leftTime.inWholeMilliseconds % 1000).toInt()
)

@Stable
data class CountdownHolder(
    val timeLeft: State<Duration>,
    val formatRes: State<FormattedRes>,
)
